import keras
import os
import numpy as np
from keras import layers
from keras import models
import matplotlib.pyplot as plt
from keras.preprocessing.image import ImageDataGenerator
from keras.models import load_model
from keras.utils.vis_utils import plot_model
import json


# Note that the depth of the feature maps(其实就是卷积核的数量) 
# is progressively increasing in the network (from 32 to 128), 
# feature map的大小的变化范围是148x148 －> 7x7(这个7x7是在代码清单５ｘ５的网络结构中)



base_dir = './fchollet/cats_and_dogs_small'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
test_dir = os.path.join(base_dir, 'test')




def result_plot(history):
    # Let's plot the loss and accuracy of the model over the training and validation data during training:
    acc = history['acc']
    val_acc = history['val_acc']
    loss = history['loss']
    val_loss = history['val_loss']
    
    epochs = range(len(acc))
    
    plt.plot(epochs, acc, 'bo', label='Training acc')
    plt.plot(epochs, val_acc, 'b', label='Validation acc')
    plt.title('Training and validation accuracy')
    plt.legend()
    plt.savefig('./5_2-1-Training-and-validation-accuracy.jpg')
    # plt.figure()
    plt.plot(epochs, loss, 'bo', label='Training loss')
    plt.plot(epochs, val_loss, 'b', label='Validation loss')
    plt.title('Training and validation loss')
    plt.legend()
    plt.savefig('./5_2-1-Training-and-validation-loss.jpg')
    # plt.show()


    #-------------------代码清单5-5-------------------------------


def train(epochs,steps_per_epoch):
    model = models.Sequential()
    model.add(layers.Conv2D(32, (3, 3), activation='relu',input_shape=(150, 150, 3)))
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Conv2D(64, (3, 3), activation='relu'))
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Conv2D(128, (3, 3), activation='relu'))
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Conv2D(128, (3, 3), activation='relu'))
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Flatten())
    model.add(layers.Dense(512, activation='relu'))
    model.add(layers.Dense(1, activation='sigmoid'))
    # 我们来看一看feature map的维度是怎么在连续层中变化的
    print(model.summary())
    # 由于我们的神经网络以ｓｉｇｍｏｉｄ单元结束
    # 我们使用二进制交叉熵作为我们ｌｏｓｓ的评估
    #-------------------代码清单5-6-------------------------------
    from keras import optimizers
    model.compile(loss='binary_crossentropy',
                  optimizer=optimizers.RMSprop(lr=1e-4),
                  metrics=['acc'])
    # 数据处理
    # 现在你已经知道，在把数据集喂给神经网络之前，数据应该格式化并且应该预处理成浮点张量
    # 当前，我们的数据是ＪＰＥＧ文件，所以让我们的神经网络读取照片的步骤大致如下：
    # 读取图片文件
    # 把ＪＰＥＧ文件转换为ＲＢＧ的像素格式
    # 然后转化为浮点张量
    #
    # 把像素值[0,255]scale为[0,1](神经网络更喜欢处理小的数值)
    # 这看起来有点daunting,但是庆幸的是，ｋｅｒａｓ有自动处理的机制。
    # Keras有一个图像处理的帮助工具，在`keras.preprocessing.image`中
    # Ｋｅｒａｓ还包含了`ImageDataGenerator`来设置ｐｙｔｈｏｎ生成器，自动把图片转换为很多ｂａｔｃｈ的预处理张量。

    # All images will be rescaled by 1./255

    #-------------------代码清单5-7-------------------------------
    train_datagen = ImageDataGenerator(rescale=1./255)
    test_datagen = ImageDataGenerator(rescale=1./255)
    train_generator = train_datagen.flow_from_directory(
            # This is the target directory
            train_dir,
            # All images will be resized to 150x150
            target_size=(150, 150),
            batch_size=20,
            # Since we use binary_crossentropy loss, we need binary labels
            class_mode='binary')

    validation_generator = test_datagen.flow_from_directory(
            validation_dir,
            target_size=(150, 150),
            batch_size=20,
            class_mode='binary')
    
    
    # 我们看看这些生成器的输出：
    # 它产生了150x150 RGB的图片(shape `(20, 150, 150, 3)`)和二进制标签(shape `(20,)`).
    # ２０是每个ｂａｔｃｈ中的样本数量（batch size）.
    
    # 注意这个生成器会不停的生成这些批量，它会不停循环当前路径中的这些图像，
    # 由于这个原因，我们需要在某个点停止循环。
    for data_batch, labels_batch in train_generator:
        print('data batch shape:', data_batch.shape)
        print('labels batch shape:', labels_batch.shape)
        break
    
    
    # 我们利用生成器来对数据进行拟合
    # 我们使用 `fit_generator` 函数，它在数据生成器上的效果和ｆｉｔ相同。
    
    
    # 总结下这些数据的关系：
    # ｂａｔｃｈ_size=20
    # ｅｐｏｃｈｓ=30
    # steps_per_epoch＝１００
    # 每个ｅｐｏｃｈ进行１００次迭代，每次迭代使用的ｂａｔｃｈ_size=20张图片
    
    
    # 使用git_generator时，你可以传入一个validation_data参数，其作用和在ｆｉｔ方法中类似。
    # 值得注意的是，这个参数可以是一个数据生成器,也可以是Numpy数组组成的元组。
    # 如果向`validation_data`传入一个生成器，那么这个生成器就可以无休止地产生验证数据，
    # 因此你还需要指定validation_steps参数，说明需要从验证生成器中抽取多少个批次用于评估。
    history = model.fit_generator(
          train_generator,
          steps_per_epoch=steps_per_epoch,
          epochs=epochs,
          validation_data=validation_generator,
          validation_steps=50)


    # It is good practice to always save your models after training:
    model.save('cats_and_dogs_small_1.h5')
    print("type(model.history)=",type(model.history))

    with open('5_2-1-history.json', 'w') as f:#保存训练过程中的记录，便于后面画画。
        json.dump(model.history.history, f)





# 图片显示，我们的验证集的预测精度只有70-72%
# 我们的验证集的ｌｏｓｓ在５个ｅｐｏｃｈｓ之后触底。

#------------------------------------------------------------------------

def top(epochs,steps_per_epoch):
    if os.path.exists('cats_and_dogs_small_1.h5')==True:#如果当前路径存在模型文件，那么就直接读取模型
        # 保存网络结构，载入网络结构
        network = load_model('cats_and_dogs_small_1.h5') 
        print("输出权重",network.get_weights())
        plot_model(network, to_file='model.png', show_shapes=True)
        print("------------------------输出权重的维度-------------------")
        weight_list=network.get_weights()
        for item in weight_list:
            print("当前权重矩阵维度是",np.shape(item))
        print("-----------------------------------------------------")
        load_dict=''
        with open("5_2-1-history.json",'r') as load_f:
            load_dict = json.load(load_f)
        result_plot(load_dict)


    else:
        train(epochs,steps_per_epoch)
    #否则，就重新开始训练模型，并且保存模型文件


if __name__ == '__main__':
    top(epochs=30,steps_per_epoch=100)
    print("训练完成")#一定要有这一句，这样在log里面就能看到是否真的结束了。
    